//
//  GL20Renderer.cpp
//  Texture
//
//  Created by Yao Melo on 2/24/13.
//
//

#include "GL20Renderer.h"
#include <Vector.h>
#include <OpenGLES/EAGL.h>
#include "ResourceFile.h"
#include "GraphicsDevice.h"
#include "OpenGLWaveFrontObject.h"
#include "OpenGLTexture3D.h"
#include "OpenGLWaveFrontGroup.h"

static inline void printErr(int line)
{
    GLenum error = glGetError();
    NSLog(@"GL20Renderer error %x,at line:%d",error,line);
    
}
///helper for texture shader
static inline void RenderMesh(OpenGLWaveFrontObject* mesh,MATRIX mat, GLuint program,GLuint vertexAttrib,GLuint texAttrib,
                              GLuint matrixHandle,GLuint texHandle)
{
    glUseProgram(program);

    glUniformMatrix4fv(matrixHandle, 1, GL_FALSE, mat.f);
    glUniform1i(texHandle, 0); //Use texture sampler 0
    
    glEnableVertexAttribArray(vertexAttrib);
    [mesh bind];
    glVertexAttribPointer(vertexAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(texAttrib);

    glVertexAttribPointer(texAttrib, 2, GL_FLOAT, GL_FALSE, 0,(void*)([mesh numberOfVertices]*3*sizeof(GLfloat)));
    for(OpenGLWaveFrontGroup *group in mesh.groups)
    {
        glDrawElements(GL_TRIANGLES, 3*group.numberOfFaces, GL_UNSIGNED_SHORT, &group.faces[0]);
    }
    
//    glDrawArrays(GL_TRIANGLES, 0, [mesh numberOfVertices]);

    glUseProgram(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

}

//helper for color shader
static inline void RenderMesh(OpenGLWaveFrontObject* mesh,MATRIX mat, GLuint program,GLuint vertexAttrib,Vec4 color,
                              GLuint matrixHandle,GLuint colorHandle)
{

    glUseProgram(program);
    
    glUniformMatrix4fv(matrixHandle, 1, GL_FALSE, mat.f);
    glUniform4f(colorHandle, color.x, color.y, color.z, color.w);
    glEnableVertexAttribArray(vertexAttrib);
    [mesh bind];
    glVertexAttribPointer(vertexAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
    for(OpenGLWaveFrontGroup *group in mesh.groups)
    {
        glDrawElements(GL_TRIANGLES, 3*group.numberOfFaces, GL_UNSIGNED_SHORT, &group.faces[0]);
    }
    
//    glDrawArrays(GL_TRIANGLES, 0, [mesh numberOfVertices]);
    glUseProgram(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
}


static inline void initMeshAndTexture(NSString* meshName,NSString* textureName,GLuint width,GLuint height, OpenGLWaveFrontObject** outMesh, OpenGLTexture3D** outTexture)
{
    NSString* resourcePath = [NSBundle mainBundle].resourcePath;
            NSLog(@"init error:%x",glGetError());
    *outTexture= [[OpenGLTexture3D alloc] initWithFilename:[NSString stringWithFormat:@"%@/%@", resourcePath,textureName] width:width height:height];
    
        NSLog(@"gen texture error:%x",glGetError());
    *outMesh = [[OpenGLWaveFrontObject alloc] initWithPath:[NSString stringWithFormat:@"%@/%@", resourcePath,meshName]];
            NSLog(@"gen mesh error:%x",glGetError());
    

}



@implementation GL20Renderer

@synthesize shipMotionMatrix;

-(id)init
{
    self = [super init];
    if(self)
    {
        //program && shaders && vertex attribs
        
        NSString* resourcePath = [NSBundle mainBundle].resourcePath;
        
        CPVRTResourceFile::SetReadPath([resourcePath UTF8String]);
        GLuint uiFragShader, uiVertShader;
        /* Gets the Data Path */
		if(ShaderLoadFromFile("blank", "/TextureFragShader.fsh", GL_FRAGMENT_SHADER, 0, &uiFragShader) == 0)
			printf("Loading the fragment shader fails");
		if(ShaderLoadFromFile("blank", "/TextureVertShader.vsh", GL_VERTEX_SHADER, 0, &uiVertShader) == 0)
			printf("Loading the vertex shader fails");
		const char* pszAttribs[] = { "a_vertex", "a_texcoord" };
		CreateProgram(&textureProgram, uiVertShader, uiFragShader, pszAttribs, 2);
		
        if(ShaderLoadFromFile("blank", "/ColorFragShader.fsh", GL_FRAGMENT_SHADER, 0, &uiFragShader) == 0)
			printf("Loading the fragment shader fails");
		if(ShaderLoadFromFile("blank", "/ColorVertShader.vsh", GL_VERTEX_SHADER, 0, &uiVertShader) == 0)
			printf("Loading the vertex shader fails");
		CreateProgram(&colorProgram, uiVertShader, uiFragShader, pszAttribs, 2);
		

        
        //uniform handles
		// First gets the location of that variable in the shader using its name
		matHandle = glGetUniformLocation(textureProgram, "u_matrix");
		texHandle = glGetUniformLocation(textureProgram, "u_texture");
        
        colorMatHandle = glGetUniformLocation(colorProgram, "u_matrix");
		colorHandle = glGetUniformLocation(colorProgram, "u_color");
        GLuint errorcode = glGetError();
        NSLog(@"init error:%x",errorcode);

        initMeshAndTexture(@"ship.obj", @"ship.png",256,256 ,&shipMesh, &shipTexture);
        initMeshAndTexture(@"invader.obj", @"invader.png",256,256 ,&invaderMesh, &invaderTextrue);

        shotMesh = [[OpenGLWaveFrontObject alloc] initWithPath:[NSString stringWithFormat:@"%@/%@", resourcePath,@"shot.obj"]];
        backgroundTexture = [[OpenGLTexture3D alloc] initWithFilename:[NSString stringWithFormat:@"%@/%@", resourcePath,@"background.jpg"]  width: 512 height:512];
        GLfloat polys[] = {
            -160.0f,240.0f,-200.0f, 0.0f,0.0f,
            -160.0f,-240.0f,-200.0f,0.0f,1.0f,
            160.0f, -240.0f,-200.0f,1.0f,1.0f,
            160.0f, 240.0f, -200.0f,1.0f,0.0f
        };
        
       
       
        glGenBuffers(1, &bgVBO);
        
        // Bind the VBO so we can fill it with data
        glBindBuffer(GL_ARRAY_BUFFER, bgVBO);
        
        // Set the buffer's data
        // Calculate verts size: (3 vertices * stride (3 GLfloats per each vertex))
        unsigned int uiSize = 4 * (sizeof(GLfloat) * 5);
        glBufferData(GL_ARRAY_BUFFER, uiSize, polys, GL_STATIC_DRAW);
        explosionTextrue = [[OpenGLTexture3D alloc] initWithFilename:[NSString stringWithFormat:@"%@/%@", resourcePath,@"explode.png"]  width: 256 height:256];
        [self generateExplosionVerticesBuffer];
        MatrixIdentity(shipMotionMatrix);
    }
    return self;
}

-(void)generateExplosionVerticesBuffer
{
    size_t mallocSize = 4 * 16 * 5*sizeof(GLfloat);
    GLfloat *vertices = (GLfloat*)malloc(mallocSize);
    GLfloat* cursor = vertices;
    for (int row = 0; row < 4; row++) {
        for (int column = 0; column < 4; column++) {
            *(cursor++) = 1;
            *(cursor++) = 1;
            *(cursor++) = 0;
            *(cursor++) = 0.25f + column * 0.25f;
            *(cursor++) = 0 + row * 0.25f;
            
            *(cursor++) = -1;
            *(cursor++) = 1;
            *(cursor++) = 0;
            *(cursor++) = 0 + column * 0.25f;
            *(cursor++) = 0 + row * 0.25f;
            
            *(cursor++) = -1;
            *(cursor++) = -1;
            *(cursor++) = 0;
            *(cursor++) = 0 + column * 0.25f;
            *(cursor++) = 0.25f + row * 0.25f;
            
            *(cursor++) = 1;
            *(cursor++) = -1;
            *(cursor++) = 0;
            *(cursor++) = 0.25f + column * 0.25f;
            *(cursor++) = 0.25f + row * 0.25f;
        }
    }
    glGenBuffers(1, &explosionVBO);
    glBindBuffer(GL_ARRAY_BUFFER, explosionVBO);
    glBufferData(GL_ARRAY_BUFFER, mallocSize, vertices, GL_STATIC_DRAW);
    free(vertices);
    
}

-(void)setCameraForShip:(Vec3)shipPosition withWidth:(float)width height:(float)height
{
    MatrixIdentity(cameraMat);
    MATRIX lookat;
    
    MatrixLookAtRH(lookat, Vec3(shipPosition.x, 6, 2), Vec3(shipPosition.x,0,-4), Vec3(0,1,0));
    MATRIX projection;
    MatrixPerspectiveFovRH(projection, f2vt(90*PIf/180.0f), f2vt(((float) width / (float) height)), f2vt(1.0f), f2vt(100.0f), 0);
    MatrixMultiply(cameraMat,  lookat,projection);
    
}

-(void)loadGameoverTexture
{
    if(!gameoverTexture)
    {
        gameoverTexture = [[OpenGLTexture3D alloc] initWithFilename:[NSString stringWithFormat:@"%@/%@", [NSBundle mainBundle].resourcePath,@"game-over.jpg"] width:512 height:512];
        
    }
    
}

-(void)loadGamewinTexture
{
    if(!youwonTexture)
    {
        youwonTexture = [[OpenGLTexture3D alloc] initWithFilename:[NSString stringWithFormat:@"%@/%@", [NSBundle mainBundle].resourcePath,@"you_won.png"] width:512 height:512];
        
    }
    
}

-(void)resetTextureForPlaying
{
    if(gameoverTexture)
    {
        [gameoverTexture release];
        gameoverTexture = nil;
    }
    if(youwonTexture)
    {
        [youwonTexture release];
        youwonTexture = nil;
    }
}

-(void)renderGameoverWithWidth:(float)width height:(float)height
{
    MATRIX ortho;
    GLfloat w = width/2;
    GLfloat h = height/2;
    MatrixOrtho(ortho, -w, w, -h, h, 1, 500);
    [gameoverTexture bind];
    glUseProgram(textureProgram);
    glUniformMatrix4fv(matHandle, 1, GL_FALSE, ortho.f);
    glUniform1i(texHandle, 0);
    
    glBindBuffer(GL_ARRAY_BUFFER, bgVBO);
    glEnableVertexAttribArray(VERTEX_ARRAY_ATTR);
    glVertexAttribPointer(VERTEX_ARRAY_ATTR, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), 0);
    glEnableVertexAttribArray(TEX_ARRAY_ATTR);
    glVertexAttribPointer(TEX_ARRAY_ATTR, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    glUseProgram(0);
}


-(void) renderGamewinWithWidth:(float)width height:(float)height
{
    MATRIX ortho;
    GLfloat w = width/2;
    GLfloat h = height/2;
    MatrixOrtho(ortho, -w, w, -h, h, 1, 500);
    [youwonTexture bind];
    glUseProgram(textureProgram);
    glUniformMatrix4fv(matHandle, 1, GL_FALSE, ortho.f);
    glUniform1i(texHandle, 0);
    
    glBindBuffer(GL_ARRAY_BUFFER, bgVBO);
    glEnableVertexAttribArray(VERTEX_ARRAY_ATTR);
    glVertexAttribPointer(VERTEX_ARRAY_ATTR, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), 0);
    glEnableVertexAttribArray(TEX_ARRAY_ATTR);
    glVertexAttribPointer(TEX_ARRAY_ATTR, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    glUseProgram(0);
}

-(void)renderBackgroundWithWidth:(float)width height:(float)height;
{
    MATRIX ortho;
    GLfloat w = width/2;
    GLfloat h = height/2;
    MatrixOrtho(ortho, -w, w, -h, h, 1, 500);
    [backgroundTexture bind];
    glUseProgram(textureProgram);

    glUniformMatrix4fv(matHandle, 1, GL_FALSE, ortho.f);
    glUniform1i(texHandle, 0);
    

    glBindBuffer(GL_ARRAY_BUFFER, bgVBO);
    glEnableVertexAttribArray(VERTEX_ARRAY_ATTR);
    
    glVertexAttribPointer(VERTEX_ARRAY_ATTR, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), 0);
    glEnableVertexAttribArray(TEX_ARRAY_ATTR);
    glVertexAttribPointer(TEX_ARRAY_ATTR, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat),(void*) (3*sizeof(GLfloat)));
    glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
    glUseProgram(0);

}

-(void)renderShip:(SimShip *)ship
{
    if(ship->isExploding)
    {
        return;
    }
    [shipTexture bind];
    MATRIX transform;

    MATRIX tmp;
    MATRIX tmp2;
    MatrixTranslation(tmp, ship->position.x, ship->position.y, ship->position.z);
    MatrixMultiply(tmp2, tmp,cameraMat);
    MatrixRotationY(tmp, PI);
    MatrixMultiply(transform, tmp,tmp2);
    tmp = transform;
    MatrixMultiply(transform, shipMotionMatrix, tmp);
    RenderMesh(shipMesh,transform, textureProgram, VERTEX_ARRAY_ATTR, TEX_ARRAY_ATTR, matHandle, texHandle);
}

-(void)renderInvaders:(std::vector<SimInvader> *)invaders withYAngle:(float)angle
{
    [invaderTextrue bind];
    MATRIX transform;
    MATRIX tmp;
    MATRIX tmp2;
    for(std::vector<SimInvader>::iterator it = invaders->begin();it!=invaders->end();)
    {
        MatrixTranslation(tmp, it->position.x, it->position.y, it->position.z);
        MatrixMultiply(tmp2,tmp, cameraMat);
        MatrixRotationY(tmp, angle);
        MatrixMultiply(transform, tmp,tmp2);
//        Vec4 invaderVec;
//        MatrixVec4Multiply(invaderVec, Vec4(0,0,0,1), transform);
//            NSLog(@"invader in NDC:%f,%f,%f",invaderVec.x,invaderVec.y,invaderVec.z);
        RenderMesh(invaderMesh, transform, textureProgram, VERTEX_ARRAY_ATTR, TEX_ARRAY_ATTR, matHandle, texHandle);
        it++;
    }

}

-(void)renderShots:(std::vector<SimShot> *)shots
{
    MATRIX transform;
    MATRIX tmp;
//    MATRIX tmp2;
    for(std::vector<SimShot>::iterator it = shots->begin();it!=shots->end();)
    {
        MatrixTranslation(tmp, it->position.x, it->position.y, it->position.z);
        MatrixMultiply(transform,  tmp,cameraMat);
//        MatrixMultiply(transform, tmp2, tmp);
        RenderMesh(shotMesh, transform, colorProgram, VERTEX_ARRAY_ATTR, Vec4(1.0f, 1.0f, 0.0f, 1.0f), colorMatHandle, colorHandle);
        it++;
    }
}

-(void)renderExplosions:(std::vector<SimExplosion> *)explosions
{
    [explosionTextrue bind];
    MATRIX transform;
    MATRIX tmp;
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    for(std::vector<SimExplosion>::iterator it = explosions->begin();it!=explosions->end();)
    {
        MatrixTranslation(tmp, it->position.x, it->position.y, it->position.z);
        MatrixMultiply(transform, tmp, cameraMat);
        glUseProgram(textureProgram);
        glUniform1i(texHandle, 0);
        glUniformMatrix4fv(matHandle, 1, GL_FALSE, transform.f);
        glBindBuffer(GL_ARRAY_BUFFER, explosionVBO);
        
        glEnableVertexAttribArray(VERTEX_ARRAY_ATTR);
        glVertexAttribPointer(VERTEX_ARRAY_ATTR, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), 0);
        glEnableVertexAttribArray(TEX_ARRAY_ATTR);
        glVertexAttribPointer(TEX_ARRAY_ATTR, 2, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat), (void*)(3*sizeof(GLfloat)));
        glDrawArrays(GL_TRIANGLE_FAN, (int)(it->aliveTime / EXPLOSION_DURATION * 15) * 4, 4);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glUseProgram(0);
        it++;
    }
    glDisable(GL_BLEND);
}

- (void)dealloc
{
    [shipMesh release];
    [invaderMesh release];
    [shotMesh release];
    [explosionMesh release];
    [shipTexture release];
    [invaderTextrue release];
    [explosionTextrue release];
    [backgroundTexture release];
    glDeleteBuffers(1, &bgVBO);
    glDeleteBuffers(1, &explosionVBO);
//    glDeleteProgram(); why can not call this?
    [super dealloc];
}


@end